package com.renfei.multimediatask.ui.task9;


import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;

import androidx.annotation.RequiresApi;

import com.renfei.multimediatask.R;
import com.renfei.multimediatask.base.BaseActivity;
import com.renfei.multimediatask.util.YUVDataUtil;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;

import butterknife.BindView;
import butterknife.OnClick;

public class VideoRecordAllProcess extends BaseActivity {
    private static final boolean DEBUG = true;    // TODO set false on release
    private static final String TAG = "VideoRecordAllProcess";

    @BindView(R.id.texture)
    AutoFitTextureView texture;
    @BindView(R.id.startRecord)
    Button startRecord;
    @BindView(R.id.stopRecord)
    Button stopRecord;
    @BindView(R.id.ll_btn_layout)
    RelativeLayout llBtnLayout;


    private CameraHelper cameraHelper;


    public String targetFilePath;

    private int logCount = 0;

    public ArrayBlockingQueue<byte[]> queue;




    private int recordWidth;
    private int recordHeight;

    private VideoRecordThread videoThread;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_video_record_all_process;
    }

    @Override
    protected void initListener() {
//        targetFilePath = getParentFilePath() + "video_encodec_exception.h264";
        targetFilePath = getParentFilePath() + "video_encodec_exception.mp4";


        queue = new ArrayBlockingQueue<>(10);

        cameraHelper = new CameraHelper(new WeakReference<>(this), texture);
        cameraHelper.setOnPreviewFrameListener(new CameraHelper.OnPreviewFrameListener() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onPreviewFrame(byte[] data, int width, int height) {
                if (logCount < 1) {

                    recordWidth = width;
                    recordHeight = height;

                    loge(TAG, "onPreviewFrame    " + data.length
                            + "     width   " + width
                            + "     height   " + height
                    );
                    logCount++;
                }
                if (null != queue) {
                    try {
                        if (queue.size() >= 9) {
                            queue.poll();
                        } else {
                            queue.put(data);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onResume() {
        super.onResume();
        cameraHelper.onResume();
    }

    @Override
    protected void onPause() {
        stopRecording();
        cameraHelper.onPause();
        super.onPause();
    }

    @OnClick({R.id.startRecord, R.id.stopRecord})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.startRecord:
                if (null == videoThread)
                    startRecording();
                break;
            case R.id.stopRecord:
                stopRecording();
                break;
        }
    }


    private void startRecording() {
        if (DEBUG) Log.v(TAG, "startRecording:");

        videoThread = new VideoRecordThread(
                new WeakReference<>(this), recordWidth, recordHeight
        );
        videoThread.start();
    }

    /**
     * request stop recording
     */
    private void stopRecording() {
        if (null != videoThread) {
            videoThread.stopEncodec();
        }
    }


    private class VideoRecordThread extends Thread {
        protected static final int TIMEOUT_USEC_INPUT = -1;
        protected static final int TIMEOUT_USEC = 10000;
        private WeakReference<VideoRecordAllProcess> weakReference;

        private int mWidth;
        private int mHeight;
        private boolean isEncode = false;


        private MediaCodec videoEncodec;
        private MediaCodec.BufferInfo videoBufferInfo;


        private MediaMuxer mediaMuxer;
        private int videoTrackIndex = -1;
        private boolean isMediaMuxerStart = false;

        public VideoRecordThread(WeakReference<VideoRecordAllProcess> weakReference, int mWidth, int mHeight) {
            this.weakReference = weakReference;
            this.mWidth = mWidth;
            this.mHeight = mHeight;
            initMediaCodec();

            initMuxer();

        }

        private void initMuxer() {

            try {
                mediaMuxer = new MediaMuxer(targetFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void initMediaCodec() {
            try {
                videoBufferInfo = new MediaCodec.BufferInfo();


                MediaFormat videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
                videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
                videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
                videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, mWidth * mHeight * 5);
                videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

                videoEncodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
                videoEncodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                videoEncodec.start();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        public void stopEncodec() {
            isEncode = false;
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void run() {
            super.run();
//            FileOutputStream fileOutputStream = null;

//            try {
//                File file = new File(weakReference.get().targetFilePath);
//                if (file.exists()) {
//                    file.delete();
//                }
//                file.createNewFile();
//                fileOutputStream = new FileOutputStream(file);
            isEncode = true;

            while (isEncode) {
                if (null != weakReference.get().queue && weakReference.get().queue.size() > 0) {

                    byte[] inputData = weakReference.get().queue.poll();
                    byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2];
                    YUVDataUtil.NV21ToNV12(inputData, yuv420sp, mWidth, mHeight);
                    inputData = yuv420sp;
//                        byte[] inputData = weakReference.get().queue.poll();

//                        fileOutputStream.write(inputData, 0, inputData.length);
                    //输入缓冲区处理
                    int inputBufferIndex = videoEncodec.dequeueInputBuffer(TIMEOUT_USEC_INPUT);
                    if (inputBufferIndex >= 0) {
                        ByteBuffer byteBuffer = videoEncodec.getInputBuffer(inputBufferIndex);
                        byteBuffer.clear();
                        byteBuffer.put(inputData);
                        videoEncodec.queueInputBuffer(inputBufferIndex, 0, inputData.length, getPTSUs(), 0);
                    } else {
                        Log.e(TAG, "输入缓冲区暂无可用: ");
                    }
                    //输出缓冲区处理
                    int outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_USEC);
                    if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                        Log.e(TAG, "输出缓冲区等待中: ");
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                        Log.e(TAG, "输出缓冲区已更改: ");
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        //输出格式更改时，重新给muxer设置

                        MediaFormat format = videoEncodec.getOutputFormat();

                        videoTrackIndex = mediaMuxer.addTrack(format);


                        mediaMuxer.start();
                        isMediaMuxerStart = true;

                        Log.e(TAG, "输出格式已更改: ");
                    } else if (outputBufferIndex < 0) {
                        Log.e(TAG, "出现其他类型的错误: ");
                    } else {
                        while (outputBufferIndex >= 0) {
                            ByteBuffer outputBuffer = videoEncodec.getOutputBuffer(outputBufferIndex);
                            //取出数据  写入H264文件使用
                            //      byte[] outData = new byte[videoBufferInfo.size];
                            //      outputBuffer.get(outData);
                            //      // flags 利用位操作，定义的 flag 都是 2 的倍数
                            //      if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // 配置相关的内容，也就是 SPS，PPS
                            //          fileOutputStream.write(outData, 0, outData.length);
                            //      } else if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { // 关键帧
                            //          fileOutputStream.write(outData, 0, outData.length);
                            //      } else {
                            //          // 非关键帧和SPS、PPS,直接写入文件，可能是B帧或者P帧
                            //          fileOutputStream.write(outData, 0, outData.length);
                            //      }

                            if (isMediaMuxerStart) {
                                outputBuffer.position(videoBufferInfo.offset);
                                outputBuffer.limit(videoBufferInfo.offset + videoBufferInfo.size);

                                videoBufferInfo.presentationTimeUs = getPTSUs();
                                //写入数据

                                mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, videoBufferInfo);

                                prevOutputPTSUs = videoBufferInfo.presentationTimeUs;

                                Log.e(TAG, "写入数据到文件。。。: " + videoBufferInfo.size);
                            } else {
                                Log.e(TAG, "isMediaMuxerStart:  mediaMuxer is not start");
                            }
                            videoEncodec.releaseOutputBuffer(outputBufferIndex, false);
                            outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_USEC);
                        }
                    }
                }
            }
//                fileOutputStream.flush();
//                fileOutputStream.close();

            mediaMuxer.stop();
            mediaMuxer.release();
            mediaMuxer = null;

            videoEncodec.stop();
            videoEncodec.release();
            videoEncodec = null;

            isEncode = false;
            isMediaMuxerStart = false;
            Log.e(TAG, "录制已经完成: ");
        }

        /**
         * previous presentationTimeUs for writing
         */
        private long prevOutputPTSUs = 0;

        /**
         * get next encoding presentationTimeUs
         *
         * @return
         */
        protected long getPTSUs() {
            long result = System.nanoTime() / 1000L;
            // presentationTimeUs should be monotonic
            // otherwise muxer fail to write
            if (result < prevOutputPTSUs)
                result = (prevOutputPTSUs - result) + result;
            return result;
        }
    }

}
